package cn.xeblog.plugin.game.gobang;

import cn.hutool.core.thread.GlobalThreadPool;
import cn.hutool.core.util.StrUtil;
import cn.xeblog.commons.entity.User;
import cn.xeblog.commons.entity.game.gobang.GobangDTO;
import cn.xeblog.commons.enums.Game;
import cn.xeblog.plugin.action.ConsoleAction;
import cn.xeblog.plugin.action.GameAction;
import cn.xeblog.plugin.annotation.DoGame;
import cn.xeblog.plugin.cache.DataCache;
import cn.xeblog.plugin.game.AbstractGame;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.ui.components.JBScrollPane;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.List;
import java.util.*;

/**
 * 五子棋
 *
 * @author anlingyi
 * @date 2020/6/5
 */
@DoGame(Game.GOBANG)
public class Gobang extends AbstractGame<GobangDTO> {

  /**
   * 行数，y
   */
  private static final int ROWS = 15;
  /**
   * 列数，x
   */
  private static final int COLS = 15;
  /**
   * 棋子总数
   */
  private static final int CHESS_TOTAL = ROWS * COLS;
  /**
   * 棋盘
   */
  private JPanel chessPanel;
  /**
   * 提示
   */
  private JLabel tips;
  /**
   * 开始界面
   */
  private JPanel startPanel;
  /**
   * 悔棋按钮
   */
  private JButton regretButton;
  /**
   * AI落子按钮
   */
  private JButton aiTestButton;
  /**
   * 游戏按钮面板
   */
  private JPanel gameButtonPanel;
  /**
   * AI自动落子
   */
  private boolean aiAutoPut = true;
  /**
   * 每个格子的边框大小
   */
  private int border;
  /**
   * 棋子大小
   */
  private int chessSize;
  /**
   * 棋盘宽度
   */
  private int width;
  /**
   * 棋盘高度
   */
  private int height;
  /**
   * 已下棋子数据
   */
  private int[][] chessData;

  /**
   * 当前已下棋子数
   */
  private int currentChessTotal;
  /**
   * 棋子类型，1黑棋 2白棋
   */
  private int type;
  /**
   * 游戏是否结束
   */
  private boolean isGameOver;
  /**
   * 游戏状态 -2.初始化 -1.待开始 0.进行中 1.赢 2.平
   */
  private int status = -2;
  /**
   * 标记是否已落子
   */
  private boolean put;
  /**
   * 高亮棋子[（"横坐标,纵坐标"）, 是否高亮]
   */
  private Map<String, Boolean> chessHighlight;
  /**
   * 当前玩家名
   */
  private String player;
  /**
   * 下一个玩家名
   */
  private String nextPlayer;
  /**
   * 游戏模式
   */
  private GameMode gameMode;
  /**
   * AI服务
   */
  private AIService aiService;
  /**
   * AI级别
   */
  private int aiLevel;
  /**
   * 记录落子数据
   */
  private Stack<Point> chessStack;
  /**
   * 最后一个落子点
   */
  private Point lastPoint;
  /**
   * AI配置
   */
  private AIService.AIConfig aiConfig;
  /**
   * AI级别
   */
  private static final Map<String, Integer> AI_LEVEL = new LinkedHashMap<>();

  /**
   * AI级别初始化
   */
  static {
    AI_LEVEL.put("AI·制杖", 1);
    AI_LEVEL.put("AI·棋跪王", 2);
    AI_LEVEL.put("AI·沟流儿", 3);
    AI_LEVEL.put("AI·林必诚", 4);
  }

  /**
   * 初始化游戏数据
   */
  private void initValue() {
    // 初始化最后落子点
    lastPoint = null;
    // 初始化已下棋子数据
    chessData = new int[COLS][ROWS];
    // 初始化当前已下棋子数
    currentChessTotal = 0;
    // 游戏未结束
    isGameOver = false;
    // 游戏状态为初始化
    status = -2;
    // 标记未下棋子
    put = false;
    // 棋子大小为棋盘的格子的0.75倍
    chessSize = Math.round(border * 0.75f);
    // 宽、高=行*格子大小
    width = ROWS * border + border;
    height = ROWS * border + border;
    chessStack = new Stack<>();
    // 初始化高亮棋子
    initChessHighLight();
  }

  /**
   * 游戏模式
   */
  @Getter
  @AllArgsConstructor
  private enum GameMode {
    /**
     * 人机模式
     */
    HUMAN_VS_PC("人机模式"),
    /**
     * 左右互搏
     */
    HUMAN_VS_HUMAN("左右互搏"),
    /**
     * 在线PK
     */
    ONLINE("在线PK"),
    /**
     * 调试模式
     */
    DEBUG("调试模式");
    /**
     * 模式名称
     */
    private String name;

    /**
     * 通过名称值配置
     *
     * @param name 待匹配的名称
     * @return 游戏模式选项
     */
    public static GameMode getMode(String name) {
      return Arrays.stream(values()).filter(mode -> mode.name.equals(name)).findFirst().orElse(HUMAN_VS_PC);
    }
  }


  @Override
  public void handle(GobangDTO body) { // 落子处理
    // 0.进行中 1.赢 2.平
    if (status > -1) {
      // 设置棋子
      setChess(new Point(body.getX(), body.getY(), body.getType()));

      // 打平
      if (type == 2) {
        // 变更当前落子玩家
        changePlayer();
      }
      // 检查游戏状态
      checkStatus(nextPlayer);
      // 如果游戏结束，跳过
      if (isGameOver) {
        return;
      }
    } else { // -2.初始化 -1.待开始
      // 设置游戏 0.进行中
      status = 0;
      // 棋子类型
      type = body.getType();
      // 当前类型是白子时，则说明当前等待黑子落下
      if (type == 2) {
        // 标记已下棋子
        put = true;
        // 变更当前落子玩家
        changePlayer();
        // 显示提示
        showTips(player + (GameAction.getNickname().equals(player) ? "(你)" : "") + "先下手为强！");
        return;
      }
    }
    // 标记为未落子
    put = false;
    showTips(player + "(你)：思考中...");
  }

  @Override
  public void playerLeft(User player) {
    // 玩家离开
    super.playerLeft(player);
    //游戏未结束且游戏初始化后
    if (!isGameOver && status > -2) {
      // 显示提示
      showTips("游戏结束：对手逃跑了~");
      // 更新游戏结束标识
      isGameOver = true;
      // 游戏按钮面板添加游戏结束按钮
      gameButtonPanel.add(getGameOverButton());
      // 更新ui
      gameButtonPanel.updateUI();
    }
  }

  /**
   * 检查游戏状态
   *
   * @param username 用户名
   */
  private void checkStatus(String username) {
    // debug模式标识
    boolean isDebug = gameMode == GameMode.DEBUG;
    // 游戏是否结束，默认是结束了
    boolean flag = true;
    // 游戏状态 -2.初始化 -1.待开始 0.进行中 1.赢 2.平
    switch (status) {
      case 1:
        // 非debug模式赢子，展示提示
        if (!isDebug) {
          showTips("游戏结束：" + username + "这个菜鸡赢了！");
        }
        break;
      case 2:
        // 非debug模式打平，展示提示
        if (!isDebug) {
          showTips("游戏结束：平局~ 卧槽？？？");
        }
        break;
      default:
        // 设置为未结束游戏
        flag = false;
        break;
    }

    isGameOver = flag;
    // 游戏结束且在线模式，添加结束游戏按钮
    if (isGameOver && gameMode == GameMode.ONLINE) {
      gameButtonPanel.add(getGameOverButton());
      gameButtonPanel.updateUI();
    }
  }

  /**
   * 初始化棋盘面板
   */
  private void initChessPanel() {
    // 初始化游戏数据
    initValue();
    // 设置玩家昵称
    player = GameAction.getNickname();
    // 离线游戏
    if (GameAction.isOfflineGame()) {
      switch (gameMode) {
        case HUMAN_VS_PC:
          // 人机
          aiService = createAI();
          // 白棋
          if (type == 2) {
            // AI已下棋子
            put = true;
            // 设置当前玩家名
            player = nextPlayer;
            // 设置下一个玩家名
            nextPlayer = GameAction.getNickname();
          }
          break;
        case HUMAN_VS_HUMAN:
          nextPlayer = "路人甲";
          // 白棋
          if (type == 2) {
            // 设置为黑棋
            type = 1;
            // 设置当前玩家名
            player = nextPlayer;
            // 设置下一个玩家名
            nextPlayer = GameAction.getNickname();
          }
          break;
        case DEBUG:
          // 设置为黑棋
          type = 1;
          // ai级别
          aiLevel = 6;
          // AI服务
          aiService = createAI();
          break;
      }
    } else { // 在线游戏
      // 已下棋子
      put = true;
      // 在线对战
      gameMode = GameMode.ONLINE;
      // 设置下一次落子的玩家
      getRoom().getUsers().forEach((k, v) -> {
        if (!v.getUsername().equals(player)) {
          nextPlayer = v.getUsername();
          return;
        }
      });
    }
    // 棋盘面板
    chessPanel = new JPanel() {
      @Override
      protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        // 渲染棋盘
        paintChessBoard(g);
      }
    };

    // 主界面大小
    Dimension mainDimension = new Dimension(width + 180, height + 50);
    mainPanel.removeAll();
    // 边界布局
    mainPanel.setLayout(new BorderLayout());
    // 设置最小大小
    mainPanel.setMinimumSize(mainDimension);

    // 提示
    tips = new JLabel("", JLabel.CENTER);
    tips.setFont(new Font("", Font.BOLD, 13));
    tips.setForeground(new Color(237, 81, 38));
    tips.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));

    chessPanel.setLayout(null);
    // 设置棋盘首选大小
    chessPanel.setPreferredSize(new Dimension(width, height));
    // 设置棋盘背景颜色
    chessPanel.setBackground(Color.LIGHT_GRAY);

    // 顶部面板（提示）
    JPanel topPanel = new JPanel();
    topPanel.add(tips);

    // 中心面板（棋盘）
    JPanel centerPanel = new JPanel();
    centerPanel.add(chessPanel);

    // 如果当前是debug模式，那么棋盘面板右边添加一块配置面板
    if (gameMode == GameMode.DEBUG) {
      JPanel rightPanel = new JPanel();
      rightPanel.add(getConfigPanel());
      mainPanel.add(rightPanel, BorderLayout.EAST);
    }

    // 底部面板（棋盘）
    JPanel bottomPanel = new JPanel();
    bottomPanel.setLayout(new BorderLayout());

    // 棋盘面板
    JPanel chessButtonPanel = new JPanel();
    bottomPanel.add(chessButtonPanel, BorderLayout.NORTH);

    // 游戏按钮面板
    gameButtonPanel = new JPanel();
    bottomPanel.add(gameButtonPanel, BorderLayout.SOUTH);

    // 在线对战
    if (gameMode == GameMode.ONLINE) {
      showTips("请等待...");
    } else {
      // 悔棋按钮
      regretButton = getRegretButton();
      chessButtonPanel.add(regretButton);

      // 重新开始按钮
      JButton restartButton = new JButton("重新开始");
      // 重新开始按钮事件
      restartButton.addActionListener(e -> {
        // 移除主面板容器
        mainPanel.removeAll();
        // 初始化开始面板，即返回开始页
        initStartPanel();
        mainPanel.updateUI();
      });

      // 添加重新开始按钮
      gameButtonPanel.add(restartButton);
      // 添加输出棋谱按钮
      gameButtonPanel.add(getOutputChessRecordButton());
      // 添加退出按钮
      gameButtonPanel.add(getExitButton());
    }

    // 按布局添加面板
    mainPanel.add(topPanel, BorderLayout.NORTH);
    mainPanel.add(centerPanel, BorderLayout.CENTER);
    mainPanel.add(bottomPanel, BorderLayout.SOUTH);
    mainPanel.updateUI();

    // debug模式标识
    boolean isDebug = gameMode == GameMode.DEBUG;
    // 显示提示
    if (isDebug) {
      showTips("调试模式");
    } else if (gameMode != GameMode.ONLINE) {
      showTips(player + (GameAction.getNickname().equals(player) ? "(你)" : "") + "先下手为强！");
    }

    // 如果为白棋且人机对战，AI落子
    if (type == 2 && gameMode == GameMode.HUMAN_VS_PC) {
      aiPutChess();
    }

    // 游戏状态默认为-1.待开始
    status = -1;
    // 棋盘鼠标监听
    chessPanel.addMouseListener(new MouseAdapter() {
      // 监听鼠标点击事件
      @Override
      public void mouseClicked(MouseEvent e) {
        // 已落子或者游戏已结束
        if (put || isGameOver) {
          return;
        }

        // 落子成功
        if (putChess(e.getX(), e.getY(), type)) {
          // 检查游戏状态
          checkStatus(player);

          // 非debug模式
          if (!isDebug) {
            // 已落子
            put = true;
            // 在线对战模式标识
            boolean isOnlineMode = gameMode == GameMode.ONLINE;
            // 未结束游戏，落子成功时，提示对手
            if (!isGameOver) {
              showTips(nextPlayer + (GameAction.getNickname().equals(nextPlayer) ? "(你)" : "") + "：思考中...");
            } else if (!isOnlineMode) { // 非在线对战，跳过
              return;
            }

            // 非在线对战或者 是在线对战并且是白棋
            if (!isOnlineMode || (isOnlineMode && type == 2)) {
              // 变更当前落子玩家
              changePlayer();
            }
          }

          switch (gameMode) {
            case ONLINE:
              // 在线对战，发送最后落子点
              send(lastPoint);
              break;
            case HUMAN_VS_PC:
              // 人机对战,ai落子
              aiPutChess();
              break;
            case DEBUG:
              // debug, ai自动落子并且ai落子不为空时
              if (aiTestButton != null && aiAutoPut) {
                // 自动落子
                aiTestButton.doClick();
                break;
              }
            case HUMAN_VS_HUMAN:
              // 人人对战,变更棋子类型
              type = 3 - type;
              // 未落子
              put = false;
              break;
          }
        }
      }
    });
  }

  /**
   * 变更当前落子玩家
   */
  private void changePlayer() {
    // 当前玩家与另一玩家置换下，即A落子了，则轮到B落子了
    String tempName = player;
    player = nextPlayer;
    nextPlayer = tempName;
  }

  /**
   * AI落子
   */
  private void aiPutChess() {
    // debug模式标识
    boolean isDebug = gameMode == GameMode.DEBUG;
    GlobalThreadPool.execute(() -> {
      // ai落子成功
      if (setChess(aiService.getPoint(chessData, lastPoint == null ? new Point(-1, -1, type) : lastPoint))) {
        // 调试模式时，
        if (isDebug) {
          showTips("调试模式");
          // 落子成功后,设置下一次落子的类型
          type = 3 - lastPoint.type;
        }

        // 检查状态，判断是赢还是输
        checkStatus(player);
        // 落子后，初始化为未下棋子
        put = false;
        // 游戏结束，跳过
        if (isGameOver) {
          return;
        }

        // 非debug模式，落子提示
        if (!isDebug) {
          showTips(nextPlayer + (GameAction.getNickname().equals(nextPlayer) ? "(你)" : "") + "：思考中...");
          changePlayer();
        }
      }
    });
  }

  /**
   * 获取悔棋按钮
   *
   * @return 悔棋按钮
   */
  private JButton getRegretButton() {
    // debug模式标识
    boolean isDebug = gameMode == GameMode.DEBUG;

    // 悔棋按钮，默认禁用
    JButton regretButton = new JButton("悔棋");
    regretButton.setEnabled(false);

    // 悔棋监听
    regretButton.addActionListener(e -> {
      // 默认一次后退2步棋
      int count = 2;
      // 已落子 或者 （ 非debug模式且游戏结束 或者 棋盘未落满即和棋 ），跳过
      if (put || (!isDebug && isGameOver || chessStack.size() < count)) {
        return;
      }

      // 移除两个棋子
      for (int i = 0; i < count; i++) {
        Point point = chessStack.pop();
        chessData[point.x][point.y] = 0;
      }

      // 总落子减2
      this.currentChessTotal -= count;

      // 没有棋子时，设置棋子类型黑棋
      if (chessStack.isEmpty()) {
        type = 1;
      } else {
        // 最后一个落子点
        lastPoint = chessStack.lastElement();
        // 设置棋子类型
        type = 3 - lastPoint.type;
      }

      // 游戏结束且为debug模式
      if (isGameOver && isDebug) {
        // 重置游戏
        isGameOver = false;
        put = false;
        status = 0;
        chessHighlight = null;
      }

      // 棋盘重置
      chessPanel.repaint();
    });

    return regretButton;
  }

  /**
   * 输出棋谱按钮
   *
   * @return 输出棋谱按钮
   */
  private JButton getOutputChessRecordButton() {
    JButton exitButton = new JButton("输出棋谱");
    exitButton.addActionListener(e -> {
      // 没有落子记录,跳过
      if (chessStack.isEmpty()) {
        return;
      }

      ConsoleAction.showSimpleMsg("===== 棋谱输出 =====");
      // 拼接落子记录
      StringBuffer sb = new StringBuffer();
      chessStack.forEach(p -> {
        sb.append(p.x).append(",").append(p.y).append(",").append(p.type).append(";");
      });
      ConsoleAction.showSimpleMsg(sb.toString());
      ConsoleAction.showSimpleMsg("===== END =====");
    });
    return exitButton;
  }

  /**
   * 获取配置面板
   *
   * @return 配置面板
   */
  private JPanel getConfigPanel() {
    JPanel configPanel = new JPanel();
    configPanel.setLayout(new GridLayout(8, 2));
    // 设置此组件的首选大小
    configPanel.setPreferredSize(new Dimension(150, 250));

    // 搜索深度文本
    JLabel label1 = new JLabel("搜索深度：");
    label1.setFont(new Font("", 1, 13));
    configPanel.add(label1);

    // 搜索深度文本框
    JTextField searchDepthInput = new JTextField();
    searchDepthInput.setText(String.valueOf(aiConfig.getDepth()));
    searchDepthInput.setColumns(5);
    searchDepthInput.addKeyListener(new KeyAdapter() {
      @Override
      public void keyTyped(KeyEvent e) {
        int keyChar = e.getKeyChar();
        // 值为0~9，不在范围内跳过
        if (keyChar < KeyEvent.VK_0 || keyChar > KeyEvent.VK_9) {
          e.consume();
        }
      }
    });
    configPanel.add(searchDepthInput);

    // 启发节点文本
    JLabel label2 = new JLabel("启发节点：");
    label2.setFont(new Font("", 1, 13));
    configPanel.add(label2);

    // 启发节点文本文本框
    JTextField maxNodesInput = new JTextField();
    maxNodesInput.setText(String.valueOf(aiConfig.getMaxNodes()));
    maxNodesInput.setColumns(5);
    maxNodesInput.addKeyListener(new KeyAdapter() {
      @Override
      public void keyTyped(KeyEvent e) {
        int keyChar = e.getKeyChar();
        // 值为0~9，不在范围内跳过
        if (keyChar < KeyEvent.VK_0 || keyChar > KeyEvent.VK_9) {
          e.consume();
        }
      }
    });
    configPanel.add(maxNodesInput);

    // 算杀深度文本
    JLabel label3 = new JLabel("算杀深度：");
    label3.setFont(new Font("", 1, 13));
    configPanel.add(label3);

    // 算杀深度文本框
    JTextField vcxDepthInput = new JTextField();
    vcxDepthInput.setText(String.valueOf(aiConfig.getVcxDepth()));
    vcxDepthInput.setColumns(5);
    vcxDepthInput.addKeyListener(new KeyAdapter() {
      @Override
      public void keyTyped(KeyEvent e) {
        int keyChar = e.getKeyChar();
        // 值为0~9，不在范围内跳过
        if (keyChar < KeyEvent.VK_0 || keyChar > KeyEvent.VK_9) {
          e.consume();
        }
      }
    });
    configPanel.add(vcxDepthInput);
    // 算杀模式文本
    JLabel label4 = new JLabel("算杀模式：");
    label4.setFont(new Font("", 1, 13));
    configPanel.add(label4);

    // 算杀模式单选按钮
    JRadioButton vcxCloseRadio = new JRadioButton("关闭", aiConfig.getVcx() == 0);
    vcxCloseRadio.setActionCommand("0");
    JRadioButton vctRadio = new JRadioButton("VCT", aiConfig.getVcx() == 1);
    vctRadio.setActionCommand("1");
    JRadioButton vcfRadio = new JRadioButton("VCF", aiConfig.getVcx() == 2);
    vcfRadio.setActionCommand("2");

    // 加入配置面板
    configPanel.add(vcxCloseRadio);
    configPanel.add(vctRadio);
    configPanel.add(vcfRadio);

    // 加入按钮组
    ButtonGroup vcxRadioGroup = new ButtonGroup();
    vcxRadioGroup.add(vcxCloseRadio);
    vcxRadioGroup.add(vctRadio);
    vcxRadioGroup.add(vcfRadio);

    // AI落子文本
    aiTestButton = new JButton("AI落子");
    aiTestButton.addActionListener(e -> {
      // 已落子或者游戏结束，跳过
      if (put || isGameOver) {
        return;
      }

      // 未落子
      put = true;
      // 最后点位为空时，棋子类型为2白棋
      if (lastPoint == null) {
        type = 2;
      }
      showTips("AI思考中...");

      // 设置ai配置
      aiConfig.setDepth(Math.min(20, Integer.parseInt(searchDepthInput.getText())));
      aiConfig.setMaxNodes(Math.min(30, Integer.parseInt(maxNodesInput.getText())));
      aiConfig.setVcx(Integer.parseInt(vcxRadioGroup.getSelection().getActionCommand()));
      aiConfig.setVcxDepth(Math.min(20, Integer.parseInt(vcxDepthInput.getText())));

      // 相关按钮设置AI配置中的配置
      searchDepthInput.setText(String.valueOf(aiConfig.getDepth()));
      maxNodesInput.setText(String.valueOf(aiConfig.getMaxNodes()));
      vcxDepthInput.setText(String.valueOf(aiConfig.getVcxDepth()));

      aiPutChess();
    });
    configPanel.add(aiTestButton);

    // 输出棋谱文本
    JButton inputChessRecordButton = new JButton("输入棋谱");
    inputChessRecordButton.addActionListener(e -> {
      // 输入棋谱对话框
      new InputChessRecordDialog().show();
    });
    configPanel.add(inputChessRecordButton);

    // 自动落子复选框
    JCheckBox autoPutCheckBox = new JCheckBox("自动落子", aiAutoPut);
    // 设置ai是否自动落子
    autoPutCheckBox.addChangeListener(e -> aiAutoPut = ((JCheckBox) e.getSource()).isSelected());
    configPanel.add(autoPutCheckBox);

    return configPanel;
  }

  /**
   * 输入棋谱对话框
   */
  private static class InputChessRecordDialog extends DialogWrapper {

    /**
     * 主面板
     */
    private JPanel main;
    /**
     * 棋谱文本
     */
    private JTextArea chessRecordText;

    InputChessRecordDialog() {
      // 对话框为其他窗口的父窗口
      super(true);
      // 标题
      setTitle("输入棋谱");
      // 显示ok和取消按钮
      setOKActionEnabled(true);
      // 固定的，不能调整大小
      setResizable(false);

      // 初始化
      super.init();
    }

    @Override
    protected @Nullable JComponent createCenterPanel() {
      main = new JPanel();
      // 设置此组件的首选大小
      main.setPreferredSize(new Dimension(200, 200));
      // 设置文本大小
      chessRecordText = new JTextArea();
      chessRecordText.setColumns(20);
      chessRecordText.setRows(10);
      // 自动换行
      chessRecordText.setLineWrap(true);
      // 加入垂直滚动条
      JBScrollPane scrollPane = new JBScrollPane(chessRecordText);
      scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
      main.add(scrollPane);
      return main;
    }

    @Override
    protected void doOKAction() {
      // 棋谱字符
      String text = chessRecordText.getText();
      // 不为空
      if (StrUtil.isNotBlank(text)) {
        Gobang gobang = (Gobang) GameAction.getAction();
        // 输入棋谱
        gobang.inputChessRecode(text);
      }

      super.doOKAction();
    }
  }

  /**
   * 输入棋谱
   *
   * @param chessRecord 棋谱
   */
  public void inputChessRecode(String chessRecord) {
    // 棋谱为空，跳过
    if (StrUtil.isBlank(chessRecord)) {
      return;
    }

    // 最后一个落子点
    Point newLastPoint = null;
    // 棋子数据
    int[][] newChessData = new int[15][15];
    // 记录落子数据
    Stack newChessStack = new Stack();
    // 总棋子数
    int newChessTotal = 0;
    try {
      // 棋谱数组
      String[] chessRecords = chessRecord.split(";");
      int len = chessRecords.length;
      for (int i = 0; i < len; i++) {
        // 棋子点位[列数，x,行数，y,棋子类型]
        String[] point = chessRecords[i].split(",");
        int x = Integer.parseInt(point[0]);
        int y = Integer.parseInt(point[1]);
        int type = Integer.parseInt(point[2]);

        // 设置指定位置的颜色，即棋子数据
        newChessData[x][y] = type;
        // 落子点
        Point p = new Point(x, y, type);
        // 记录数据
        newChessStack.push(p);
        // 棋子数累加
        newChessTotal++;

        // 记录最后的点位
        if (i == len - 1) {
          newLastPoint = p;
        }
      }

      chessData = newChessData;
      currentChessTotal = newChessTotal;
      chessStack = newChessStack;
      lastPoint = newLastPoint;
      // 棋子类型
      type = 3 - lastPoint.type;
      chessHighlight = null;
      status = 0;
      isGameOver = false;
      // 显示悔棋按钮
      regretButton.setEnabled(true);

      // 重画棋盘面板
      chessPanel.repaint();
      // 检查是否5连
      checkWinner(lastPoint);
      // 检查游戏状态
      checkStatus(null);
    } catch (Exception e) {
      ConsoleAction.showSimpleMsg("输入的棋谱数据无法识别！");
    }
  }

  /**
   * 绘制棋盘
   *
   * @param g 画笔工具
   */
  private void paintChessBoard(Graphics g) {
    Graphics2D g2 = (Graphics2D) g;
    // 设置颜色
    g2.setColor(Color.BLACK);
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    // 画横线
    for (int i = 0; i < ROWS; i++) {
      g2.drawLine(border, i * border + border, width - border, i * border + border);
    }

    // 画纵线
    for (int i = 0; i < COLS; i++) {
      g2.drawLine(i * border + border, border, i * border + border, height - border);
    }

    if (ROWS == 15 && COLS == 15) {
      // 标准棋盘
      int starSize = border / 3;
      int halfStarSize = starSize / 2;
      g2.fillOval(4 * border - halfStarSize, 4 * border - halfStarSize, starSize, starSize);
      g2.fillOval(12 * border - halfStarSize, 4 * border - halfStarSize, starSize, starSize);
      g2.fillOval(4 * border - halfStarSize, 12 * border - halfStarSize, starSize, starSize);
      g2.fillOval(12 * border - halfStarSize, 12 * border - halfStarSize, starSize, starSize);
      g2.fillOval(8 * border - halfStarSize, 8 * border - halfStarSize, starSize, starSize);
    }

    // 当前已下棋子数0，跳过
    if (currentChessTotal == 0) {
      return;
    }

    // 画棋子
    for (int i = 0; i < COLS; i++) {
      for (int j = 0; j < ROWS; j++) {
        // 棋子类型
        int k = chessData[i][j];
        // 棋子非黑即白，当棋子被移除后,k为0
        if (k == 0) {
          continue;
        }
        // 黑棋
        if (k == 1) {
          g2.setColor(Color.BLACK);
        } else if (k == 2) {
          // 白棋
          g2.setColor(Color.WHITE);
        }

        // 计算棋子外矩形左上顶点坐标
        int halfBorder = chessSize / 2;
        int chessX = i * border + border - halfBorder;
        int chessY = j * border + border - halfBorder;

        // 绘制实心圆
        g2.fillOval(chessX, chessY, chessSize, chessSize);

        // 高亮棋子未高亮或者当等于最后一个落子点时，设置高亮
        if (isHighlight(i, j) || i == lastPoint.x && j == lastPoint.y) {
          // 当前棋子高亮
          g2.setColor(Color.RED);
          // 绘制圆型
          g2.drawOval(chessX, chessY, chessSize, chessSize);
        }
      }
    }
  }

  /**
   * 初始化开始面板
   */
  private void initStartPanel() {
    this.aiService = null;
    mainPanel.removeAll();

    // 组件开启，设置可见，最小大小
    mainPanel.setLayout(null);
    mainPanel.setEnabled(true);
    mainPanel.setVisible(true);
    mainPanel.setMinimumSize(new Dimension(150, 400));

    // 设置开始面板
    startPanel = new JPanel();
    startPanel.setBounds(10, 10, 120, 400);
    mainPanel.add(startPanel);

    // 游戏模式文本
    JLabel label1 = new JLabel("游戏模式：");
    label1.setFont(new Font("", 1, 13));
    startPanel.add(label1);

    // 游戏模式按钮
    JRadioButton humanVsPCRadio = new JRadioButton(GameMode.HUMAN_VS_PC.getName(), true);
    humanVsPCRadio.setActionCommand(humanVsPCRadio.getText());
    JRadioButton humanVsHumanRadio = new JRadioButton(GameMode.HUMAN_VS_HUMAN.getName(), false);
    humanVsHumanRadio.setActionCommand(humanVsHumanRadio.getText());
    JRadioButton debugRadio = new JRadioButton(GameMode.DEBUG.getName(), false);
    debugRadio.setActionCommand(debugRadio.getText());

    // 单选
    ButtonGroup modeRadioGroup = new ButtonGroup();
    modeRadioGroup.add(humanVsPCRadio);
    modeRadioGroup.add(humanVsHumanRadio);
    modeRadioGroup.add(debugRadio);

    // 加入开始面板
    startPanel.add(humanVsPCRadio);
    startPanel.add(humanVsHumanRadio);
    startPanel.add(debugRadio);

    // 选择AI文本
    JLabel label4 = new JLabel("选择AI：");
    label4.setFont(new Font("", 1, 13));
    startPanel.add(label4);

    // AI难度级别
    ComboBox chessAIBox = new ComboBox();
    for (String ai : AI_LEVEL.keySet()) {
      chessAIBox.addItem(ai);
    }
    // 默认选择为：AI·沟流儿
    chessAIBox.setSelectedIndex(2);
    startPanel.add(chessAIBox);

    // 选择棋子文本
    JLabel label2 = new JLabel("选择棋子：");
    label2.setFont(new Font("", 1, 13));
    startPanel.add(label2);

    // 棋子颜色单选按钮
    JRadioButton blackChessRadio = new JRadioButton("黑棋", true);
    blackChessRadio.setActionCommand("1");
    JRadioButton whiteChessRadio = new JRadioButton("白棋", false);
    whiteChessRadio.setActionCommand("2");

    // 加入按钮组
    ButtonGroup chessRadioGroup = new ButtonGroup();
    chessRadioGroup.add(blackChessRadio);
    chessRadioGroup.add(whiteChessRadio);

    // 加入开始面板
    startPanel.add(blackChessRadio);
    startPanel.add(whiteChessRadio);

    // 棋盘大小文本
    JLabel label3 = new JLabel("棋盘尺寸：");
    label3.setFont(new Font("", 1, 13));
    startPanel.add(label3);

    // 棋盘大小下拉选择
    ComboBox chessSizeBox = new ComboBox();
    chessSizeBox.addItem("小");
    chessSizeBox.addItem("中");
    chessSizeBox.addItem("大");
    // 默认为中
    chessSizeBox.setSelectedItem("中");
    // 加入开始面板
    startPanel.add(chessSizeBox);

    // 开始游戏按钮
    JButton startGameButton = new JButton("开始游戏");
    // 开始游戏按钮行为监听
    startGameButton.addActionListener(e -> {
      // 移除开始面板
      mainPanel.remove(startPanel);
      // 游戏模式
      gameMode = GameMode.getMode(modeRadioGroup.getSelection().getActionCommand());
      // 棋子颜色
      type = Integer.parseInt(chessRadioGroup.getSelection().getActionCommand());
      // 棋盘大小
      String chessSize = chessSizeBox.getSelectedItem().toString();
      switch (chessSize) {
        case "小":
          border = 12;
          break;
        case "中":
          border = 14;
          break;
        case "大":
          border = 16;
          break;
      }

      // 人机
      if (gameMode == GameMode.HUMAN_VS_PC) {
        String ai = chessAIBox.getSelectedItem().toString();
        // 设置ai级别
        aiLevel = AI_LEVEL.get(ai);
        // 另一个玩家名称
        nextPlayer = ai;
      }

      // 初始化棋盘
      initChessPanel();
    });

    // 添加按钮
    startPanel.add(startGameButton);

    // 用户在线时，添加创建房间按钮
    if (DataCache.isOnline) {
      List<Integer> numsList = new ArrayList();
      numsList.add(2);
      startPanel.add(getCreateRoomButton(numsList));
    }
    // 添加退出按钮
    startPanel.add(getExitButton());
    mainPanel.updateUI();
  }

  @Override
  protected void init() {
    // 初始化开始面板
    initStartPanel();
  }

  @Override
  protected void start() {
    // 格子的边框默认14
    border = 14;
    // 初始化棋盘面板
    initChessPanel();

    // 这块代码只在联机时执行，在单机下不执行start方法。
    if (getRoom() == null) {
      allPlayersGameStarted();
    }
  }

  @Override
  protected void allPlayersGameStarted() { // 此时游戏状态还是初始化,这里发送消息只是为了确定棋子类型。具体逻辑在handle(GobangDTO body)方法中
    // 如果是房主时，发送消息，让白棋等待黑棋落子
    if (isHomeowner()) {
      // 自旋等待一段时间，再发送游戏数据
      invoke(() -> {
        // 随机棋子类型
        int randomType = new Random().nextInt(2) + 1;
        GobangDTO msg = new GobangDTO();
        msg.setType(3 - randomType);
        sendMsg(msg);
        handle(new GobangDTO(0, 0, randomType));
      }, 500);
    }
  }

  /**
   * 落子
   *
   * @param x    列数，x
   * @param y    行数，y
   * @param type 棋子类型 1.黑 2.白
   * @return true, 落子成功，否则，false
   */
  public boolean putChess(int x, int y, int type) {
    if (isGameOver) {
      return false;
    }

    // 计算出对应的行列 四舍五入取整
    int row = Math.round((float) (x - border) / border);
    int col = Math.round((float) (y - border) / border);

    // 不在范围内，跳过
    if (row < 0 || col < 0 || row > ROWS - 1 || col > COLS - 1) {
      return false;
    }

    // 棋子圆心坐标
    int circleX = row * border + border;
    int circleY = col * border + border;

    // 判断鼠标点击的坐标是否在棋子圆外
    boolean notInCircle = Math.pow(circleX - x, 2) + Math.pow(circleY - y, 2) > Math.pow((double) chessSize / 2, 2);

    if (notInCircle) {
      // 不在棋子圆内
      return false;
    }

    // 设置棋子
    return setChess(new Point(row, col, type));
  }

  /**
   * 落下棋子
   *
   * @param point 棋子点位
   * @return true, 落子成功，false，落子失败
   */
  private boolean setChess(Point point) {
    // 点位已有棋子，跳过
    if (chessData[point.x][point.y] != 0) {
      return false;
    }

    // 设置最后的点位，当前棋子总数+1,设置棋子位置,入栈
    lastPoint = point;
    currentChessTotal++;
    chessData[point.x][point.y] = point.type;
    chessStack.push(point);

    // 悔棋按钮，只有在人机才出现
    if (regretButton != null) {
      // 存在棋子，并且为（debug或者人类VS人类或者为非当前棋子类型)时，显示悔棋按钮，并获得焦点
      regretButton.setEnabled(currentChessTotal > 1 && (gameMode == GameMode.DEBUG || gameMode == GameMode.HUMAN_VS_HUMAN
          || point.type != this.type));
      regretButton.requestFocus();
    }

    // 重绘
    chessPanel.repaint();

    // 检查是否5连
    checkWinner(point);

    return true;
  }

  /**
   * 检查是否和棋
   */
  public void checkPeace() {
    // 当无子可下时，打平
    if (currentChessTotal == CHESS_TOTAL) {
      peacemaker();
    }
  }

  /**
   * 检查是否5连
   *
   * @param point 棋子点位
   */
  public void checkWinner(Point point) {
    int x = point.x;
    int y = point.y;
    int type = point.type;

    // 初始化高亮棋子
    initChessHighLight();
    // 连子数
    int k = 1;

    // 横纵从左至右，即[x1,x2,x3,x4,x5]→
    for (int i = 1; i < 5; i++) {
      // 横纵从左至右，如果最后一个子连成五连，即[x1,x2,x3,x4,x5]→ ,其中落子顺序为x1,x2,x3,x4,x5,最后一子x5向后4步，k=5,表示有五连
      int preX = x - i;
      // 超出边界，跳出
      if (preX < 0) {
        break;
      }

      // 如果类型不一样，跳出
      if (chessData[preX][y] != type) {
        break;
      }

      // 设置棋子高亮
      setChessHighlight(preX, y);

      // 五连获胜
      if (++k == 5) {
        winner();
        return;
      }
    }

    // 横纵从右至左，即[x5,x4,x3,x2,x1]←
    for (int i = 1; i < 5; i++) {
      // 横纵从右至左，如果最后一个子连成五连，即[x5,x4,x3,x2,x1] ← ,其中落子顺序为x1,x2,x3,x4,x5,最后一子x5向前4步，k=5,表示有五连
      int nextX = x + i;

      // 超出边界，跳出
      if (nextX > ROWS - 1) {
        break;
      }

      // 如果类型不一样，跳出
      if (chessData[nextX][y] != type) {
        break;
      }

      // 设置棋子高亮
      setChessHighlight(nextX, y);

      // 五连获胜
      if (++k == 5) {
        winner();
        return;
      }
    }

    // 初始化高亮棋子
    initChessHighLight();
    // 连子数
    k = 1;
    //              [  ,  ,x1,  ,  ]
    //              [  ,  ,x2,  ,  ]
    //              [  ,  ,x3,  ,  ]
    //              [  ,  ,x4,  ,  ]
    // 纵轴从上至下，即[  ,  ,x5,  ,  ]↓
    for (int i = 1; i < 5; i++) {
      //              [  ,  ,x1,  ,  ]
      //              [  ,  ,x2,  ,  ]
      //              [  ,  ,x3,  ,  ]
      //              [  ,  ,x4,  ,  ]
      // 纵轴从上至下，即[  ,  ,x5,  ,  ]↓,其中落子顺序为x1,x2,x3,x4,x5,最后一子x5向上4步，k=5,表示有五连
      int preY = y - i;

      // 超出边界，跳出
      if (preY < 0) {
        break;
      }

      // 如果类型不一样，跳出
      if (chessData[x][preY] != type) {
        break;
      }

      // 设置棋子高亮
      setChessHighlight(x, preY);

      // 五连获胜
      if (++k == 5) {
        winner();
        return;
      }
    }

    //              [  ,  ,x5,  ,  ]
    //              [  ,  ,x4,  ,  ]
    //              [  ,  ,x3,  ,  ]
    //              [  ,  ,x2,  ,  ]
    // 纵轴从下而上，即[  ,  ,x1,  ,  ]↑,
    for (int i = 1; i < 5; i++) {
      //              [  ,  ,x5,  ,  ]
      //              [  ,  ,x4,  ,  ]
      //              [  ,  ,x3,  ,  ]
      //              [  ,  ,x2,  ,  ]
      // 纵轴从下而上，即[  ,  ,x1,  ,  ]↑,其中落子顺序为x1,x2,x3,x4,x5,最后一子x5向下4步，k=5,表示有五连
      int nextY = y + i;

      // 超出边界，跳出
      if (nextY > COLS - 1) {
        break;
      }

      // 如果类型不一样，跳出
      if (chessData[x][nextY] != type) {
        break;
      }

      // 设置棋子高亮
      setChessHighlight(x, nextY);

      if (++k == 5) {
        winner();
        return;
      }
    }

    // 初始化高亮棋子
    initChessHighLight();
    // 连子数
    k = 1;
    //                       [x1,  ,  ,  ,  ]
    //                       [  ,x2,  ,  ,  ]
    //                       [  ,  ,x3,  ,  ]
    //                       [  ,  ,  ,x4,  ]
    // 左对角线，纵轴左上而右下,即[  ,  ,  ,  ,x5]↘
    for (int i = 1; i < 5; i++) {

      //               [x1,  ,  ,  ,  ]
      //               [  ,x2,  ,  ,  ]
      //               [  ,  ,x3,  ,  ]
      //               [  ,  ,  ,x4,  ]
      // 纵轴左上而右下,即[  ,  ,  ,  ,x5]↘,其中落子顺序为x1,x2,x3,x4,x5,最后一子x5坐右下至左上4步，k=5,表示有五连
      int preX = x - i;
      int preY = y - i;

      // 超出边界，跳出
      if (preX < 0 || preY < 0) {
        break;
      }

      // 如果类型不一样，跳出
      if (chessData[preX][preY] != type) {
        break;
      }

      // 设置棋子高亮
      setChessHighlight(preX, preY);

      // 五连获胜
      if (++k == 5) {
        winner();
        return;
      }
    }

    //                 [x5,  ,  ,  ,  ]
    //                 [  ,x4,  ,  ,  ]
    //                 [  ,  ,x3,  ,  ]
    //                 [  ,  ,  ,x2,  ]
    // 纵轴从右下至左上，即[  ,  ,  ,  ,x1]↖,
    for (int i = 1; i < 5; i++) {
      //                 [x5,  ,  ,  ,  ]
      //                 [  ,x4,  ,  ,  ]
      //                 [  ,  ,x3,  ,  ]
      //                 [  ,  ,  ,x2,  ]
      // 纵轴从右下至左上，即[  ,  ,  ,  ,x1]↖,其中落子顺序为x1,x2,x3,x4,x5,最后一子x5从左上至右下4步，k=5,表示有五连
      int nextX = x + i;
      int nextY = y + i;

      // 超出边界，跳出
      if (nextX > ROWS - 1 || nextY > COLS - 1) {
        break;
      }

      // 如果类型不一样，跳出
      if (chessData[nextX][nextY] != type) {
        break;
      }

      // 设置棋子高亮
      setChessHighlight(nextX, nextY);

      // 五连获胜
      if (++k == 5) {
        winner();
        return;
      }
    }

    // 初始化高亮棋子
    initChessHighLight();
    // 连子数
    k = 1;

    //                    [  ,  ,  ,  ,x5]
    //                    [  ,  ,  ,x4,  ]
    //                    [  ,  ,x3,  ,  ]
    //                    [  ,x2,  ,  ,  ]
    // 右对角线从右上至左下,即[x1,  ,  ,  ,  ]↙
    for (int i = 1; i < 5; i++) {

      //                    [  ,  ,  ,  ,x5]
      //                    [  ,  ,  ,x4,  ]
      //                    [  ,  ,x3,  ,  ]
      //                    [  ,x2,  ,  ,  ]
      // 右对角线从右上至左下,即[x1,  ,  ,  ,  ]↙,其中落子顺序为x1,x2,x3,x4,x5,最后一子x5从右上至左下4步，k=5,表示有五连
      int nextX = x + i;
      int preY = y - i;

      // 超出边界，跳出
      if (nextX > ROWS - 1 || preY < 0) {
        break;
      }

      // 如果类型不一样，跳出
      if (chessData[nextX][preY] != type) {
        break;
      }

      // 设置棋子高亮
      setChessHighlight(nextX, preY);

      // 五连获胜
      if (++k == 5) {
        winner();
        return;
      }
    }

    //                    [  ,  ,  ,  ,x5]
    //                    [  ,  ,  ,x4,  ]
    //                    [  ,  ,x3,  ,  ]
    //                    [  ,x2,  ,  ,  ]
    // 右对角线从右下至左上,即[x1,  ,  ,  ,  ]↗
    for (int i = 1; i < 5; i++) {
      //                   [  ,  ,  ,  ,x5]
      //                   [  ,  ,  ,x4,  ]
      //                   [  ,  ,x3,  ,  ]
      //                   [  ,x2,  ,  ,  ]
      // 右对角线从左下至右上即[x1,  ,  ,  ,  ]↗，最后一子x5从右上至左下4步，k=5,表示有五连
      int preX = x - i;
      int nextY = y + i;

      // 超出边界，跳出
      if (preX < 0 || nextY > COLS - 1) {
        break;
      }

      // 如果类型不一样，跳出
      if (chessData[preX][nextY] != type) {
        break;
      }

      // 设置棋子高亮
      setChessHighlight(preX, nextY);

      // 五连获胜
      if (++k == 5) {
        winner();
        return;
      }
    }

    // 检查是否和棋
    checkPeace();
    // 初始化高亮棋子
    initChessHighLight();
  }


  /**
   * 获胜，设置当前状态为1
   */
  private void winner() {
    chessPanel.repaint();
    status = 1;
  }

  /**
   * 平手，设置当前状态为平手
   */
  private void peacemaker() {
    status = 2;
  }

  /**
   * 发送棋子相关信息
   *
   * @param point 棋子点位
   */
  private void send(Point point) {
    // 组装并发送游戏数据
    GobangDTO dto = new GobangDTO();
    dto.setX(point.x);
    dto.setY(point.y);
    dto.setType(point.type);
    sendMsg(dto);
  }

  /**
   * 显示提示
   *
   * @param msg 提示语
   */
  private void showTips(String msg) {
    // 游戏结束或者提示文本为空时，跳过
    if (isGameOver || tips == null) {
      return;
    }

    tips.setText(msg);
  }

  /**
   * 初始化高亮棋子
   */
  private void initChessHighLight() {
    chessHighlight = new HashMap<>();
  }

  /**
   * 设置棋子高亮
   *
   * @param x 横坐标
   * @param y 纵坐标
   */
  private void setChessHighlight(int x, int y) {
    this.chessHighlight.put(x + "," + y, true);
  }

  /**
   * 棋子是否高亮
   *
   * @param x 横坐标
   * @param y 纵坐标
   * @return true, 高亮.否则, false
   */
  private boolean isHighlight(int x, int y) {
    // 高亮棋子为空，跳过
    if (chessHighlight == null) {
      return false;
    }

    // 拼装key并判断是否存在
    return chessHighlight.containsKey(x + "," + y);
  }

  /**
   * 创建AI服务
   *
   * @return AI服务
   */
  private AIService createAI() {
    // debug标识
    boolean debug = gameMode == GameMode.DEBUG;
    // 实例化配置
    aiConfig = new AIService.AIConfig(6, 10, debug, 0, 8);

    switch (aiLevel) {
      case 1:
        // "AI·制杖"  , 1
        aiConfig.setDepth(1);
        break;
      case 2:
        // "AI·棋跪王", 2
        aiConfig.setDepth(4);
        break;
      case 3:
        // "AI·沟流儿", 3
        aiConfig.setDepth(6);
        aiConfig.setVcx(1);
        break;
      case 4:
        // "AI·林必诚", 4
        aiConfig.setDepth(8);
        aiConfig.setVcx(1);
        aiConfig.setVcxDepth(10);
        break;
    }

    // AI服务
    return new ZhiZhangAIService(aiConfig);
  }

}